<?php

require_once( ORM_DIR.'/StoredObject.php' );
require_once( ORM_DIR.'/storage/Storage.php' );

/**
 * Хранилище объектов на основе реляционной базы данных
 * 
 * @author alno
 */
abstract class DbStorage implements Storage {
	
	public abstract function query( $sql );
	
	public abstract function fetch( $query );
	
	public abstract function getInsertedId();
	
	public function refresh( StoredObject & $obj ) {
		
		// Получаем дескриптор объекта
		$desc = & $obj->getDescriptor();
		
		// Запрос к БД
		$query = $this->query( "SELECT * FROM ".$desc->getTableName()." WHERE ".$desc->getPrimaryKeyName()." = '".addslashes( $obj->getPrimaryKey() )."'" );
		
		// Получить объект из запроса
		$data = $this->fetch( $query );
		
		// Сохранить данные в объект
		$obj->mergeFields( $data );
		
		// Связываем объект с текущим хранилищем
		$obj->setStorage( $this );
		
		return $obj;
	}
	
	/**
	 * Сохранить объект в БД
	 *
	 * @param StoredObject $obj объект, который необходимо сохранить
	 * @return StoredObject сохраненный объект
	 */
	public function save( StoredObject & $obj ) {
		
		// Получаем дескриптор объекта
		$desc = & $obj->getDescriptor();
		
		// Получаем значения полей объекта
		$fields = & $obj->getFields();
		
		// Начинаем строить текст SQL-запроса
		$sql = "UPDATE ".$desc->getTableName()." SET ";
		
		$first = true;
		foreach( $fields as $fieldName => $fieldValue ) if ( $fieldName != $desc->getPrimaryKeyName() ) {
			if ( !$first ) $sql .= ", ";			
			$first = false;
			
			$sql .= $fieldName." = ";
			
			if ( $fieldValue === null ) {
				$sql .= "NULL";
			} else {
				$sql .= "'".addslashes( $fieldValue )."'";
			}
		}
		
		$sql .= " WHERE ".$desc->getPrimaryKeyName()." = '".addslashes( $obj->getPrimaryKey() )."'";
		
		// Запрос к БД
		$this->query( $sql );
				
		// Связываем объект с текущим хранилищем
		$obj->setStorage( $this );
		
		return $obj;
	}
	
	/**
	 * Найти в БД объект заданного типа, удовлетворяющий выражению SQL
	 * 
	 * @param string exp выражение SQL
	 * @return StoredObject объект
	 */
	public function findByExpression( StoredObjectDescriptor & $desc, $exp ) {
		
		// Строим запрос SQL
		$sql = "SELECT * FROM ".$desc->getTableName()." WHERE ".$exp;
		
		// Создаем новый экземпляр объекта
		$obj = $desc->createObject();
		
		// Запрос к БД
		$query = $this->query( $sql );
		
		// Получить объект из запроса
		$data = $this->fetch( $query );
		
		// Сохранить данные в объект
		$obj->mergeFields( $data );
		
		// Связываем объект с текущим хранилищем
		$obj->setStorage( $this );
		
		return $obj;
	}
	
	public function findByPrimaryKey( StoredObjectDescriptor & $desc, $primaryKey ) {		
		return $this->findByExpression( $desc, $desc->getPrimaryKeyName()." = '".addslashes( $primaryKey )."'" );
	}
	
	public function findByFields( StoredObjectDescriptor & $desc, array $fields ) {
		return $this->findByExpression( $desc, $this->buildExpressionByFields( $fields ) );
	}

	/**
	 * Найти в БД все объекты заданного типа, удовлетворяющие выражению SQL
	 * 
	 * @param string exp выражение SQL
	 * @return array массив объектов
	 */
	public function findAllByExpression( StoredObjectDescriptor & $desc, $exp ) {
		
		$objects = array();
		
		// Строим запрос SQL
		$sql = "SELECT * FROM ".$desc->getTableName()." WHERE ".$exp;
		
		// Запрос к БД
		$query = $this->query( $sql );
		
		while( $data = $this->fetch( $query ) ) {
			
			// Создаем новый экземпляр объекта
			$obj = $desc->createObject();
			
			// Сохранить данные в объект
			$obj->mergeFields( $data );
			
			// Связываем объект с текущим хранилищем
			$obj->setStorage( $this );
			
			// Добавляем в результирующий массив
			array_push( $objects, $obj );
		}
		
		return $objects;
	}
	
	public function findAllByFields( StoredObjectDescriptor & $desc, array $fields ) {	
		return $this->findAllByExpression( $desc, $this->buildExpressionByFields( $fields ) );
	}
	
	/**
	 * Найти в БД все объекты заданного типа
	 * 
	 * @return array массив объектов
	 */
	public function findAll( StoredObjectDescriptor & $desc ) {
		return $this->findAllByExpression( $desc, "1" );
	}
	
	/**
	 * Создать объект в БД
	 *
	 * @param StoredObject $obj объект, который необходимо создать
	 * @return StoredObject созданный объект
	 */
	public function create( StoredObject & $obj ) {
		
		// Получаем дескриптор объекта
		$desc = & $obj->getDescriptor();
		
		// Получаем значения полей объекта
		$fields = & $obj->getFields();
		
		// Строим запрос
		$sql = "INSERT INTO ".$desc->getTableName()." ( ";
		
		$first = true;
		foreach( $fields as $fieldName => $fieldValue ) if ( $fieldName != $desc->getPrimaryKeyName() ) {
			if ( !$first ) $sql .= ", ";			
			$first = false;
			
			$sql .= $fieldName;
		}
		
		$sql .= " ) VALUES ( ";
		
		$first = true;
		foreach( $fields as $fieldName => $fieldValue ) if ( $fieldName != $desc->getPrimaryKeyName() ) {
			if ( !$first ) $sql .= ", ";			
			$first = false;
			
			if ( $fieldValue === null ) {
				$sql .= "NULL";
			} else {
				$sql .= "'".addslashes( $fieldValue )."'";
			}
		}
		
		$sql .= " )";
		
		// Запрос к БД
		$this->query( $sql );
		
		// Устанавливаем идентификатор объекта
		$obj->setPrimaryKey( $this->getInsertedId() );
		
		// Связываем объект с текущим хранилищем
		$obj->setStorage( $this );
		
		return $obj;
	}
	
	/**
	 * Удалить объект из БД
	 *
	 * @param StoredObject $obj объект, который необходимо удалить
	 */
	public function remove( StoredObject & $obj ) {
		$this->query( "DELETE FROM ".$obj->getDescriptor()->getTableName()." WHERE ".$obj->getDescriptor()->getPrimaryKeyName()." = '".addslashes( $obj->getPrimaryKey() )."'" );
	}
	
	public function removeAll( StoredObjectDescriptor & $desc ) {
		$this->query( "TRUNCATE TABLE ".$desc->getTableName() );
	}
	
	private function buildExpressionByFields( array $fields ) {
		
		// Строим выражение для выборки
		$exp = "1 ";
		
		foreach ( $fields as $fieldName => $fieldValue ) {
			$exp .= " AND ".$fieldName." = '".$fieldValue."'";
		}
		
		return $exp;
	}
}
?>
